﻿using System.Linq;
using Borg;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Text;
using System.Text.RegularExpressions;

public class DotGenerator
{
    private readonly string _blankLineStr;
    private readonly ExtensionColorHander _colorHandler;
    private const int DefaultPlugPos = 2;
    private static readonly Color ExtensionColor = Color.FromArgb(0xff, 220, 220, 220);
    private readonly ICollection<ExtensionInfo> _extensions;
    private readonly string _extensionStr;
    private readonly ICollection<string> _ignoredExtensions;
    private readonly ICollection<string> _ignoredExtensionsRegExps;
    private static readonly Color MarkedExentsionColor = Color.FromArgb(0xff, 0xb0, 0xc4, 0xde);
    private readonly ICollection<int> _markedExtensions;
    private readonly string _objectInfoStr;
    private const string PlugOnly = "<TD COLSPAN=\"2\" HEIGHT=\"10\"></TD>";
    private static readonly Color PlugPointerColor = Color.FromArgb(0xff, 0, 0, 0);
    private readonly int _plugPosition;
    private readonly string _plugStr;
    private static readonly Color BorgSlotColor = Color.FromArgb(0xff, 0xad, 0xff, 0x2f);
    private StringBuilder _sb;
    private const string SlotAndPlug = "<TD WIDTH=\"10\"></TD>";
    private static readonly Color SlotColor = Color.FromArgb(0xff, 0xf5, 0xf5, 0xf5);
    private readonly string _slotOnly;
    private const string SlotStr = "<TD ALIGN=\"RIGHT\" BGCOLOR=\"";

    private DotGenerator(ICollection<ExtensionInfo> exts, ICollection<string> ignored,
        ICollection<int> marked, int plugPos, ExtensionColorHander handler)
    {
        if (plugPos < 0 || plugPos > 2)
            throw new ArgumentException("PlugPos must be between 0 and 2, but was: " + plugPos);

        _extensions = exts;
        _ignoredExtensions = ignored;
        _markedExtensions = marked;
        _plugPosition = plugPos;

        _colorHandler = handler ?? GetExtensionColor;

        switch (plugPos)
        {
            case 0:
                _extensionStr = "<TR><TD COLSPAN=\"2\" ALIGN=\"LEFT\">";
                _objectInfoStr = "<TR><TD COLSPAN=\"2\" ALIGN=\"LEFT\">";
                _blankLineStr = "<TR><TD COLSPAN=\"2\" HEIGHT=\"10\"></TD></TR>";
                _plugStr = null;
                _slotOnly = "<TD WIDTH=\"10\"></TD>";
                break;

            case 1:
                _extensionStr = "<TR><TD HEIGHT=\"10\" BGCOLOR=\"white\"></TD><TD COLSPAN=\"2\" ALIGN=\"LEFT\">";
                _objectInfoStr = "<TR><TD HEIGHT=\"10\" BGCOLOR=\"white\"></TD><TD COLSPAN=\"2\" ALIGN=\"LEFT\">";
                _blankLineStr = "<TR><TD HEIGHT=\"10\" BGCOLOR=\"white\"></TD><TD COLSPAN=\"2\" HEIGHT=\"10\"></TD></TR>";
                _plugStr = "<TD ALIGN=\"LEFT\" PORT=\"P";
                _slotOnly = "<TD HEIGHT=\"10\" BGCOLOR=\"white\"></TD><TD WIDTH=\"10\"></TD>";
                break;

            case 2:
                _extensionStr = "<TR><TD HEIGHT=\"10\" BGCOLOR=\"white\"></TD><TD COLSPAN=\"3\" ALIGN=\"LEFT\">";
                _objectInfoStr = "<TR><TD HEIGHT=\"10\" BGCOLOR=\"white\"></TD><TD COLSPAN=\"3\" ALIGN=\"LEFT\">";
                _blankLineStr = "<TR><TD HEIGHT=\"10\" BGCOLOR=\"white\"></TD><TD COLSPAN=\"3\" HEIGHT=\"10\"></TD></TR>";
                _plugStr = "<TD COLSPAN=\"2\" ALIGN=\"LEFT\" PORT=\"P";
                _slotOnly = "<TD HEIGHT=\"10\" BGCOLOR=\"white\"></TD><TD COLSPAN=\"2\" WIDTH=\"10\"></TD>";
                break;
        }
        
        _ignoredExtensionsRegExps = new List<string>();

        foreach (var ext in _ignoredExtensions.Where(ext => ext.StartsWith("/") && ext.EndsWith("/")))
            _ignoredExtensionsRegExps.Add(ext);
    }

    private void AddExtension(ExtensionInfo extension)
    {
        AddExtensionHeader(extension);
        AddExtensionLabel(extension);
        AddExtensionFooter();
        AddPlugPointers(extension);
    }

    private void AddExtensionFooter()
    {
        _sb.AppendLine("shape=plaintext");
        _sb.AppendLine("]");
    }

    private void AddExtensionHeader(ExtensionInfo extension)
    {
        _sb.AppendLine(GetExtensionNodeId(extension));
        _sb.AppendLine(" [");
    }

    private void AddExtensionLabel(ExtensionInfo extension)
    {
        AddExtensionLabelHeader(extension);
        AddExtensionNodeLabelInfo(extension);
        AddExtensionNodeLabelSlotsAndPlugs(extension);
        AddExtensionNodeLabelFooter();
    }

    private void AddExtensionLabelHeader(ExtensionInfo extension)
    {
        _sb.AppendLine("label=<");
        _sb.Append("<TABLE PORT=\"Main\" BORDER=\"0\" CELLSPACING=\"0\" CELLPADDING=\"4\" BGCOLOR=\"");
        _sb.Append(ColorToHtmlString(_colorHandler(extension)));
        _sb.AppendLine("\">");
    }

    private void AddExtensionNodeLabelFooter()
    {
        _sb.AppendLine("</TABLE>>,");
    }

    private void AddExtensionNodeLabelInfo(ExtensionInfo extension)
    {
        _sb.Append(_extensionStr);
        _sb.Append(extension.Name);
        _sb.Append(" (");
        _sb.Append(extension.Id);
        _sb.AppendLine(")</TD></TR>");

        var info = extension.Object as IObjectInfo;

        if (info != null)
        {
            _sb.Append(_objectInfoStr);
            _sb.Append(HTMLCharacterConverter.CreateHtmlSaveStringFrom(info.Name));
            _sb.AppendLine("</TD></TR>");
        }
        
        _sb.AppendLine(_blankLineStr);
    }

    private void AddExtensionNodeLabelSlotsAndPlugs(ExtensionInfo ei)
    {
        if (_plugPosition == 0)
            AddOnlySlots(ei);
        else
            AddSlotsAndPlugs(ei);
    }

    private void AddExtensions()
    {
        foreach (var ei in _extensions.Where(ei => !IsIgnoredExtension(ei)))
            AddExtension(ei);
    }

    private void AddFooter()
    {
        _sb.AppendLine("}");
    }

    private void AddHeader()
    {
        _sb.AppendLine("digraph HotViz {");
        _sb.AppendLine("rankdir=RL;");
        _sb.AppendLine();
        _sb.AppendLine("node [");
        _sb.AppendLine("fontsize=10,");
        _sb.AppendLine("fontname=\"Tahoma\",");
        _sb.AppendLine("label=\"\",");
        _sb.AppendLine("fontcolor=\"black\"");
        _sb.AppendLine("]");
        _sb.AppendLine();
    }

    private void AddOnlySlots(ExtensionInfo extension)
    {
        foreach (var si in extension.SlotInfos)
        {
            _sb.Append("<TR>");
            _sb.Append(_slotOnly);
            _sb.Append("<TD ALIGN=\"RIGHT\" BGCOLOR=\"");
            _sb.Append(ColorToHtmlString(GetSlotColor(si.Name)));
            _sb.Append("\" PORT=\"S");
            _sb.Append(si.Id);
            _sb.Append("\">");

            if (!si.IsOpen)
                _sb.Append("<FONT COLOR=\"RED\">");

            _sb.Append(si.Name.Replace("Borg.", ""));
            _sb.Append(" (");
            _sb.Append(si.Id);
            _sb.Append(")");

            if (!si.IsOpen)
                _sb.Append("</FONT>");

            _sb.AppendLine("</TD></TR>");
        }
        
        _sb.AppendLine("<TR><TD COLSPAN=\"2\" HEIGHT=\"10\"></TD></TR>");
    }

    private void AddPlug(RepositoryElement plug)
    {
        _sb.Append(_plugStr);
        _sb.Append(plug.Id);
        _sb.Append("\">");
        _sb.Append(plug.Name.Replace("Borg.", ""));
        _sb.Append(" (");
        _sb.Append(plug.Id);
        _sb.Append(")</TD>");
    }

    private void AddPlugPointer(string fromNodeId, PlugInfo fromPlug, SlotInfo toSlot)
    {
        string mainInfo;

        if (_plugPosition == 0)
            mainInfo = string.Format("{0}:Main -> I{1}:S{2}:e [",
                fromNodeId, toSlot.ExtensionInfo.Id, toSlot.Id);
        else
            mainInfo = string.Format("{0}:P{1}:w -> I{2}:S{3}:e [",
                new object[] {fromNodeId, fromPlug.Id, toSlot.ExtensionInfo.Id, toSlot.Id});
        
        _sb.AppendLine(mainInfo);

        var colorInfo = string.Format("color = \"{0}\", dir = forward, arrowhead = normal, arrowtail = {1} ]",
            ColorToHtmlString(PlugPointerColor), toSlot.SelectedPlugs.Contains(fromPlug) ? "box" : "none");

        _sb.AppendLine(colorInfo);
    }

    private void AddPlugPointers(ExtensionInfo extension)
    {
        var extensionNodeId = GetExtensionNodeId(extension);

        foreach (var pi in extension.PlugInfos)
            foreach (var si in pi.PluggedInSlots)
                AddPlugPointer(extensionNodeId, pi, si);
    }

    private void AddRestOfPlugs(ExtensionInfo ei)
    {
        for (var i = ei.SlotInfos.Count; i < ei.PlugInfos.Count; i++)
        {
            _sb.AppendLine("<TR>");
            AddPlug(ei.PlugInfos[i]);
            _sb.Append("<TD COLSPAN=\"2\" HEIGHT=\"10\"></TD>");
            _sb.AppendLine("</TR>");
        }
    }

    private void AddRestOfSlots(ExtensionInfo ei)
    {
        for (var i = ei.PlugInfos.Count; i < ei.SlotInfos.Count; i++)
        {
            _sb.AppendLine("<TR>");
            _sb.Append(_slotOnly);
            AddSlot(ei.SlotInfos[i]);
            _sb.AppendLine("</TR>");
        }
    }

    private void AddSlot(SlotInfo slot)
    {
        _sb.Append("<TD ALIGN=\"RIGHT\" BGCOLOR=\"");
        _sb.Append(ColorToHtmlString(GetSlotColor(slot.Name)));
        _sb.Append("\" PORT=\"S");
        _sb.Append(slot.Id);
        _sb.Append("\">");

        if (!slot.IsOpen)
            _sb.Append("<FONT COLOR=\"RED\">");

        _sb.Append(slot.Name.Replace("Borg.", ""));
        _sb.Append(" (");
        _sb.Append(slot.Id);
        _sb.Append(")");

        if (!slot.IsOpen)
            _sb.Append("</FONT>");

        _sb.Append("</TD>");
    }

    private void AddSlotsAndPlugs(ExtensionInfo ei)
    {
        AddSymmetricSlotsAndPlugs(ei);
        AddRestOfSlots(ei);
        AddRestOfPlugs(ei);
        _sb.AppendLine(_blankLineStr);
    }

    private void AddSymmetricSlotsAndPlugs(ExtensionInfo ei)
    {
        var min = Math.Min(ei.SlotInfos.Count, ei.PlugInfos.Count);

        for (var i = 0; i < min; i++)
        {
            _sb.AppendLine("<TR>");
            AddPlug(ei.PlugInfos[i]);
            _sb.Append("<TD WIDTH=\"10\"></TD>");

            AddSlot(ei.SlotInfos[i]);
            _sb.AppendLine("</TR>");
        }
    }

    private static string ColorToHtmlString(Color color)
    {
        return string.Format("#{0:x2}{1:x2}{2:x2}", color.R, color.G, color.B);
    }

    public static string GetDotString()
    {
        return GetDotString(null);
    }

    public static string GetDotString(ExtensionColorHander colorHander)
    {
        ReadOnlyCollection<ExtensionInfo> extensionInfos =
            Runtime.Repository.ExtensionInfos;

        ICollection<string> ignored = new Collection<string>();
        ICollection<int> marked = new Collection<int>();

        return GetDotString(extensionInfos, ignored, marked, 2, colorHander);
    }

    public static string GetDotString(ICollection<ExtensionInfo> extensions,
        ICollection<string> ignored, ICollection<int> marked, int plugPos)
    {
        return GetDotString(extensions, ignored, marked, plugPos, null);
    }

    public static string GetDotString(ICollection<ExtensionInfo> extensions,
        ICollection<string> ignored, ICollection<int> marked, int plugPos, ExtensionColorHander colorHander)
    {
        var generator =
            new DotGenerator(extensions, ignored, marked, plugPos, colorHander);

        return generator.MakeDotString();
    }

    private Color GetExtensionColor(ExtensionInfo extension)
    {
        return _markedExtensions.Contains(extension.Id)
            ? MarkedExentsionColor
            : ExtensionColor;
    }

    private static string GetExtensionNodeId(RepositoryElement ei)
    {
        return ("I" + ei.Id);
    }

    private static Color GetSlotColor(string name)
    {
        return name.StartsWith("Borg.")
            ? BorgSlotColor
            : SlotColor;
    }

    private bool IsIgnoredExtension(RepositoryElement extension)
    {
        if (_ignoredExtensions.Contains(extension.Name))
            return true;

        return _ignoredExtensionsRegExps.Any(regex =>
            Regex.Match(extension.Name, regex.Replace("/", string.Empty)).Success);
    }

    private string MakeDotString()
    {
        _sb = new StringBuilder();

        AddHeader();
        AddExtensions();
        AddFooter();

        return _sb.ToString();
    }
}

